package com.ryder.petmatediarybackend.module.file.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ryder.petmatediarybackend.common.config.db.MinioProperties;
import com.ryder.petmatediarybackend.common.exception.BusinessException;
import com.ryder.petmatediarybackend.module.file.entity.SysFileRecord;
import com.ryder.petmatediarybackend.module.file.enums.FileBizTypeEnum;
import com.ryder.petmatediarybackend.module.file.service.FileUploadService;
import com.ryder.petmatediarybackend.module.file.service.SysFileRecordService;
import com.ryder.petmatediarybackend.module.file.vo.FileUploadVO;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * 描述: 文件上传服务实现类
 *
 * @author Ryder
 * @version 1.0
 * @since 1.0
 * 更新日期: 2025/10/30
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileUploadServiceImpl implements FileUploadService {

    private final MinioClient minioClient;
    private final MinioProperties minioProperties;
    private final SysFileRecordService sysFileRecordService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FileUploadVO> uploadFiles(List<MultipartFile> files, String bizType, Long userId) {
        // 1. 校验业务类型
        FileBizTypeEnum bizTypeEnum = FileBizTypeEnum.fromCode(bizType);
        if (bizTypeEnum == null) {
            throw new BusinessException("不支持的业务类型: " + bizType);
        }

        // 2. 校验权限（使用SaToken）
        String requiredPermission = bizTypeEnum.getRequiredPermission();
        if (!StpUtil.hasPermission(requiredPermission)) {
            log.warn("[文件上传] 权限不足, userId={}, bizType={}, requiredPermission={}", 
                    userId, bizType, requiredPermission);
            throw new BusinessException(String.format("无权限上传该类型文件，需要权限: %s", requiredPermission));
        }
        log.info("[文件上传] 权限校验通过, userId={}, bizType={}, permission={}", 
                userId, bizType, requiredPermission);

        // 3. 校验文件数量
        if (files == null || files.isEmpty()) {
            throw new BusinessException("请选择要上传的文件");
        }
        if (bizTypeEnum.isCountExceeded(files.size())) {
            throw new BusinessException(String.format("最多只能上传%d个文件", bizTypeEnum.getMaxCount()));
        }

        // 4. 如果是头像类型，先删除旧头像
        if (bizTypeEnum.isAvatar()) {
            deleteOldAvatar(userId, bizType);
        }

        // 5. 上传文件
        List<FileUploadVO> result = new ArrayList<>();
        for (MultipartFile file : files) {
            FileUploadVO vo = uploadSingleFile(file, bizTypeEnum, userId);
            result.add(vo);
        }

        log.info("[文件上传] 上传成功, userId={}, bizType={}, 文件数量={}", userId, bizType, files.size());
        return result;
    }

    /**
     * 上传单个文件
     */
    private FileUploadVO uploadSingleFile(MultipartFile file, FileBizTypeEnum bizTypeEnum, Long userId) {
        try {
            // 1. 校验文件 不能为空，之类的
            validateFile(file, bizTypeEnum);

            // 2. 生成文件路径
            String originalFilename = file.getOriginalFilename();
            String fileExtension = getFileExtension(originalFilename);
            String storagePath = generateStoragePath(bizTypeEnum, userId, fileExtension);

            // 3. 上传到MinIO
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(minioProperties.getBucket())
                            .object(storagePath)
                            .stream(file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build()
            );

            // 4. 生成访问URL
            String fileUrl = String.format("%s/%s/%s", 
                    minioProperties.getEndpoint(), 
                    minioProperties.getBucket(), 
                    storagePath);

            // 5. 保存文件记录
            SysFileRecord record = new SysFileRecord();
            record.setUserId(userId);
            record.setBizType(bizTypeEnum.getCode());
            record.setFileKey(storagePath);
            record.setFileUrl(fileUrl);
            record.setFileName(originalFilename);
            record.setFileSize(file.getSize());
            record.setFileType(file.getContentType());
            record.setStoragePath(storagePath);
            sysFileRecordService.save(record);

            // 6. 返回结果
            return FileUploadVO.builder()
                    .fileKey(storagePath)
                    .url(fileUrl)
                    .fileName(originalFilename)
                    .fileSize(file.getSize())
                    .fileType(file.getContentType())
                    .build();

        } catch (Exception e) {
            log.error("[文件上传] 上传失败, userId={}, fileName={}, error={}", 
                    userId, file.getOriginalFilename(), e.getMessage(), e);
            throw new BusinessException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 校验文件
     */
    private void validateFile(MultipartFile file, FileBizTypeEnum bizTypeEnum) {
        if (file.isEmpty()) {
            throw new BusinessException("文件不能为空");
        }

        // 校验文件大小
        if (bizTypeEnum.isFileSizeExceeded(file.getSize())) {
            throw new BusinessException(String.format("文件大小不能超过%s", bizTypeEnum.getMaxFileSizeDesc()));
        }

        // 校验文件格式
        String originalFilename = file.getOriginalFilename();
        if (StrUtil.isBlank(originalFilename)) {
            throw new BusinessException("文件名不能为空");
        }

        String fileExtension = getFileExtension(originalFilename);
        if (!bizTypeEnum.isFormatAllowed(fileExtension)) {
            throw new BusinessException(String.format("不支持的文件格式，仅支持: %s", bizTypeEnum.getAllowedFormats()));
        }
    }

    /**
     * 生成存储路径
     */
    private String generateStoragePath(FileBizTypeEnum bizTypeEnum, Long userId, String fileExtension) {
        String pathPrefix = bizTypeEnum.getPathPrefix();
        String timestamp = String.valueOf(System.currentTimeMillis());
        String random = IdUtil.fastSimpleUUID().substring(0, 6);
        
        // 判断是否需要日期目录
        if (bizTypeEnum == FileBizTypeEnum.PET_ALBUM 
                || bizTypeEnum == FileBizTypeEnum.DIARY_IMAGE 
                || bizTypeEnum == FileBizTypeEnum.MOMENT_IMAGE
                || bizTypeEnum == FileBizTypeEnum.SHOP_PET_COLLECTION
                || bizTypeEnum == FileBizTypeEnum.SHOP_PRODUCT_COLLECTION) {
            String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            return String.format("%s/%s/%s/%s_%s.%s", pathPrefix, userId, date, timestamp, random, fileExtension);
        } else {
            return String.format("%s/%s/%s_%s.%s", pathPrefix, userId, timestamp, random, fileExtension);
        }
    }

    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String filename) {
        if (StrUtil.isBlank(filename)) {
            return "";
        }
        int lastDotIndex = filename.lastIndexOf(".");
        if (lastDotIndex == -1) {
            return "";
        }
        return filename.substring(lastDotIndex + 1).toLowerCase();
    }

    /**
     * 删除旧头像
     */
    private void deleteOldAvatar(Long userId, String bizType) {
        LambdaQueryWrapper<SysFileRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysFileRecord::getUserId, userId)
                .eq(SysFileRecord::getBizType, bizType)
                .orderByDesc(SysFileRecord::getCreateTime);
        
        List<SysFileRecord> oldRecords = sysFileRecordService.list(wrapper);
        if (!oldRecords.isEmpty()) {
            // 注意：这里只删除数据库记录，不删除 MinIO 中的文件
            // 原因：避免误删用户历史头像文件，保留文件便于后续可能的恢复需求
            log.info("[文件清理] 清理旧头像数据库记录, userId={}, bizType={}, 记录数={}", 
                    userId, bizType, oldRecords.size());
            
            // 逻辑删除数据库记录
            sysFileRecordService.removeByIds(oldRecords.stream().map(SysFileRecord::getId).toList());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFile(String fileKey, Long userId) {
        // 1. 校验删除权限（使用SaToken）
        if (!StpUtil.hasPermission("file:delete")) {
            log.warn("[文件删除] 权限不足, userId={}, fileKey={}", userId, fileKey);
            throw new BusinessException("无权限删除文件，需要权限: file:delete");
        }
        log.info("[文件删除] 权限校验通过, userId={}, fileKey={}", userId, fileKey);

        // 2. 查询文件记录
        LambdaQueryWrapper<SysFileRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysFileRecord::getFileKey, fileKey)
                .eq(SysFileRecord::getUserId, userId);
        
        SysFileRecord record = sysFileRecordService.getOne(wrapper);
        if (record == null) {
            throw new BusinessException("文件不存在或无权限删除");
        }

        // 3. 从MinIO删除
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(minioProperties.getBucket())
                            .object(fileKey)
                            .build()
            );
        } catch (Exception e) {
            log.error("[文件删除] MinIO删除失败, fileKey={}, error={}", fileKey, e.getMessage(), e);
            throw new BusinessException("文件删除失败: " + e.getMessage());
        }

        // 4. 逻辑删除数据库记录
        sysFileRecordService.removeById(record.getId());
        
        log.info("[文件删除] 删除成功, userId={}, fileKey={}", userId, fileKey);
    }
}
